package com.lilong.jwt.common.base;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.lilong.jwt.common.exception.ExceptionEnum;
import com.lilong.jwt.common.exception.HydrauException;
import com.lilong.jwt.common.util.ValidationResult;
import com.lilong.jwt.common.util.ValidationUtils;
import com.lilong.jwt.entity.user.User;
import com.lilong.jwt.pojo.query.RequestBean;
import com.lilong.jwt.pojo.view.PageBean;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.StringPath;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

/**
 * 通用服务实现
 *
 * @param <T>
 */
public abstract class BaseServiceImpl<T extends BaseEntity> implements BaseService<T> {
    protected abstract String getEntityMsg();

    /**
     * 获取服务具体DAO
     */
    protected abstract BaseRepository<T> getBaseRepository();

    private BooleanBuilder initBooleanBuilder(Object qObject, T t) {
        JSONObject jo = (JSONObject) JSONObject.toJSON(t);
        Class cls = com.querydsl.core.types.dsl.BeanPath.class;
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        Lists.newArrayList(jo.keySet()).forEach(p -> {
            if (!ObjectUtils.isEmpty(jo.get(p))) {
                Method method;
                try {
                    if (jo.get(p) instanceof String) {
                        method = cls.getDeclaredMethod("createString", String.class);
                        method.setAccessible(true);
                        StringPath path = (StringPath) method.invoke(qObject, p);
                        booleanBuilder.and(path.like("%" + jo.getString(p) + "%"));
                    } else if (jo.get(p) instanceof Integer) {
                        method = cls.getDeclaredMethod("createNumber", String.class);
                        method.setAccessible(true);
                        NumberPath<Integer> path = (NumberPath<Integer>) method.invoke(qObject, p);
                        booleanBuilder.and(path.eq(jo.getInteger(p)));
                    } else if (jo.get(p) instanceof Long) {
                        method = cls.getDeclaredMethod("createNumber", String.class);
                        method.setAccessible(true);
                        NumberPath<Long> path = (NumberPath<Long>) method.invoke(qObject, p);
                        booleanBuilder.and(path.eq(jo.getLong(p)));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        return booleanBuilder;
    }

    /**
     * 获取默认查询条件
     */
    protected BooleanBuilder getBooleanBuilder(T t) {
        return initBooleanBuilder(getQEntity(), t);
    }

    protected abstract EntityPathBase<T> getQEntity();

    /**
     * 用于数据插入及更新时是否可以操作
     * 比如验证当前数据是否存在，有效性等
     * 如果验证不能过则抛出HydrauException异常
     */
    protected void check(T t) {
        ValidationResult result = ValidationUtils.validateEntity(t);
        if (result.isHasErrors()) {
            throw new HydrauException(result, getEntityMsg());
        }
    }


    private String[] getAllNullField(Object obj) throws Exception {
        Class stuCla = obj.getClass();// 得到类对象
        Field[] fs = stuCla.getDeclaredFields();//得到属性集合
        Set<String> set = Sets.newHashSet();
        for (Field f : fs) {//遍历属性
            f.setAccessible(true); // 设置属性是可以访问的(私有的也可以)
            if (f.get(obj) == null) {//只要有1个属性不为空,那么就不是所有的属性值都为空
                set.add(f.getName());
            }
        }
        return Joiner.on(",").skipNulls().join(set).split(",");
    }

    /**
     * 用于更新数据时校验数据有效性
     */
    protected T checkUpdate(T t) throws Exception {
        if (ObjectUtils.isEmpty(t.getId())) {
            throw new HydrauException(ExceptionEnum.OBJECT_ID_IS_NULL, getEntityMsg());
        }

        T old = getBaseRepository().findOne(t.getId());

        if (ObjectUtils.isEmpty(old)) {
            throw new HydrauException(ExceptionEnum.NOT_EXIST, getEntityMsg());
        }
        //把新传入对象不为空的值，复制到目标对像
        BeanUtils.copyProperties(t, old, getAllNullField(t));
        return old;
    }

    @Override
    public T saveEntity(T t, User operator) {
        check(t);
        t.setCreateTime(System.currentTimeMillis());
        t.setUpdateTime(System.currentTimeMillis());
        t.setCreateId(operator.getId());
        t.setUpdateId(operator.getId());
        getBaseRepository().save(t);
        return t;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchSaveEntity(List<T> t, User operator) {
        getBaseRepository().save(t);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchRemoveEntity(List<Long> ids) {
        if(!ObjectUtils.isEmpty(ids))
        {
            ids.forEach(this::removeEntity);
        }
        return 1;
    }

    public int removeEntity(Long id) {
        getBaseRepository().delete(id);
        return 1;
    }

    @Override
    public int updateEntity(T t, User operator) {
        try {
            T newT = checkUpdate(t);
            newT.setUpdateId(operator.getUpdateId());
            newT.setUpdateTime(System.currentTimeMillis());
            getBaseRepository().saveAndFlush(newT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return 1;
    }

    @Override
    public T findById(Long id) {
        if (ObjectUtils.isEmpty(id)) {
            throw new HydrauException(ExceptionEnum.OBJECT_ID_IS_NULL, getEntityMsg());
        }
        T t = getBaseRepository().findOne(id);
        if (ObjectUtils.isEmpty(t)) {
            throw new HydrauException(ExceptionEnum.DELETE, getEntityMsg());
        }
        return t;
    }

    @Override
    public List<T> query(T t) {
        Iterable<T> all = getBaseRepository().findAll(getBooleanBuilder(t));
        return Lists.newArrayList(all);
    }

    @Override
    public PageBean<T> queryByPage(RequestBean<T> t) {
        Pageable pageable = new PageRequest(t.getPageNum(), t.getPageSize(), Sort.Direction.DESC, "id");
        Page<T> page = getBaseRepository().findAll(getBooleanBuilder(t.getData()), pageable);
        System.out.println(page.getSize());
        PageBean<T> pageBean = new PageBean<>();
        BeanUtils.copyProperties(page, pageBean);
        pageBean.setDatas(Lists.newArrayList(page.iterator()));
        return pageBean;
    }
}
